﻿using System;
using System.Collections.Generic;
using System.Xml.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using BlawkayEngine;
using BlawkayEngine.Input;
using Microsoft.Xna.Framework.Input;

namespace BlawkayHockey
{
    public class Camera
    {

        #region Constructor

        public Camera()
        {
            ResetCamera();
        }

        protected void ResetCamera()
        {
            Position = Vector3.One;

            Origin = Vector3.Zero;
            CurrentOrigin = Origin;

            Zoom = 30.0f;
            CurrentZoom = Zoom;

            Vector3 oDirection = Vector3.One;
            oDirection.Normalize();
            Position = oDirection * Zoom;

            Rotation_Azimuth = MathHelper.PiOver4;
            Rotation_Elevation = -MathHelper.PiOver4;

            CurrentRotation_Azimuth = MathHelper.PiOver4;
            CurrentRotation_Elevation = -MathHelper.PiOver4;

            UpdateProjectionMatrix();
        }
        protected void CreateEffect(Game oGame)
        {
            GraphicsDevice oDevice = oGame.GraphicsDevice;
            m_oEffect = new BasicEffect(oDevice);
            m_oEffect.World = Matrix.Identity;
            m_oEffect.View = View;
            m_oEffect.Projection = Projection;
            m_oEffect.VertexColorEnabled = true;
        }
        public void CloneCamera(out Camera oClonedCamera)
        {
            oClonedCamera = new Camera();
            oClonedCamera.m_oEffect = m_oEffect;
            oClonedCamera.Position = Position;
            oClonedCamera.Origin = Origin;
            oClonedCamera.CurrentOrigin = CurrentOrigin;
            oClonedCamera.Rotation_Azimuth = Rotation_Azimuth;
            oClonedCamera.CurrentRotation_Azimuth = Rotation_Azimuth;
            oClonedCamera.Rotation_Elevation = Rotation_Elevation;
            oClonedCamera.CurrentRotation_Elevation = Rotation_Elevation;
            oClonedCamera.Zoom = Zoom;
            oClonedCamera.CurrentZoom = Zoom;
        }

        #endregion

        #region Methods

        public void Update(Game oGame, Vector3? oPosition)
        {
            UpdateViewMatrix();

            if (GameInfo.IsDebug)
            {
                UpdateInput(2.0f / 60.0f, oGame);
            }
            else
            {
                UpdateMovement(1.0f / 60.0f, Vector3.Zero, oPosition);
            }

            UpdateTransformation(oGame);
        }

        public void UpdateInput(float fDeltaTime, Game oGame)
        {
            KeyboardInput oInput = GameInfo.KeyboardInput;
            bool UpKey = oInput.IsPressed(Keys.Up);
            bool DownKey = oInput.IsPressed(Keys.Down);
            bool LeftKey = oInput.IsPressed(Keys.Left);
            bool RightKey = oInput.IsPressed(Keys.Right);

            if ((UpKey || DownKey || LeftKey || RightKey) == false)
                return;

            bool CtrlKey = oInput.IsOnePressed(Keys.LeftControl, Keys.RightControl);
            bool ShiftKey = oInput.IsOnePressed(Keys.LeftShift, Keys.RightShift);

            float ZoomSpeed = fDeltaTime * 10.0f;
            float TranslateSpeed = fDeltaTime * 50.0f; //* (Zoom / 30.0f);
            float RotateSpeed = fDeltaTime * 0.5f; //* (30.0f / Zoom);

            Vector3 vForward = VectorHelper.FlatDirection(CurrentOrigin - Position);
            Vector3 vRight = new Vector3(vForward.Z, 0.0f, -vForward.X);
            Vector3 vDirection = Vector3.Zero;

            // Rotation
            if (CtrlKey)
            {
                RotateSpeed *= 5.0f;
                if (UpKey)
                {
                    Rotation_Elevation -= RotateSpeed;
                }
                else if (DownKey)
                {
                    Rotation_Elevation += RotateSpeed;
                }
                else if (LeftKey)
                {
                    Rotation_Azimuth -= RotateSpeed;
                }
                else if (RightKey)
                {
                    Rotation_Azimuth += RotateSpeed;
                }
            }

            // Zoom
            else if (ShiftKey)
            {
                ZoomSpeed *= 20.0f;
                if (UpKey)
                {
                    Zoom -= ZoomSpeed;
                }
                else if (DownKey)
                {
                    Zoom += ZoomSpeed;
                }
            }

            // Movement
            else
            {
                if (UpKey)
                {
                    vDirection += vForward;
                }
                if (DownKey)
                {
                    vDirection -= vForward;
                }
                if (LeftKey)
                {
                    vDirection += vRight;
                }
                if (RightKey)
                {
                    vDirection -= vRight;
                }

                if (vDirection != Vector3.Zero)
                {
                    vDirection.Normalize();
                    Origin += vDirection * TranslateSpeed;
                }
            }

        }

        public void UpdateMovement(float fDeltaTime, Vector3 oDestination, Vector3? oPosition)
        {
            if (oPosition == null)
                return;

            Origin = (Vector3)oPosition;
        }

        protected void UpdateTransformation(Game oGame)
        {
            float fElevationLimit = 1.98f * MathHelper.PiOver4;
            float fZoomLimit = 10.0f;
            float fLerpRate = 0.1f;

            Zoom = MathHelper.Max(Zoom, fZoomLimit);

            Rotation_Elevation = MathHelper.Min(Rotation_Elevation, fElevationLimit);
            Rotation_Elevation = MathHelper.Max(Rotation_Elevation, -fElevationLimit);

            CurrentRotation_Azimuth = MathHelper.Lerp(CurrentRotation_Azimuth, Rotation_Azimuth, fLerpRate);
            if (Math.Abs(Rotation_Azimuth - CurrentRotation_Azimuth) < 0.0001)
                CurrentRotation_Azimuth = Rotation_Azimuth;
            
            CurrentRotation_Elevation = MathHelper.Lerp(CurrentRotation_Elevation, Rotation_Elevation, fLerpRate);
            if (Math.Abs(Rotation_Elevation - CurrentRotation_Elevation) < 0.0001)
                CurrentRotation_Elevation = Rotation_Elevation;
            
            CurrentZoom = MathHelper.Lerp(CurrentZoom, Zoom, fLerpRate);
            if (Math.Abs(Zoom - CurrentZoom) < 0.01f)
                CurrentZoom = Zoom;

            CurrentOrigin = Vector3.Lerp(CurrentOrigin, Origin, fLerpRate);
            if (Vector3.Distance(CurrentOrigin, Origin) < 0.01f)
                CurrentOrigin = Origin;

        }

        public void ZoomBy(float fAmount)
        {
            Zoom += fAmount;
        }

        public void ZoomTo(float fAmount)
        {
            Zoom = fAmount;
        }

        public void Draw(Game oGame)
        {
            if (m_oEffect == null)
                CreateEffect(oGame);

            m_oEffect.View = View;
            m_oEffect.Projection = Projection;

            // Draws Circle
            DrawBigX(oGame);
        }

        void DrawBigX(Game oGame)
        {
            int count = 12;
            int size = 3;
            Color oNegative = Color.LightGray;
            VertexPositionColor[] m_oVertexData = new VertexPositionColor[count];

            Matrix oTransform = Matrix.CreateTranslation(CurrentOrigin);
            m_oVertexData[0] = (new VertexPositionColor(Vector3.Transform(new Vector3(0, 0, size), oTransform), Color.Blue));
            m_oVertexData[1] = (new VertexPositionColor(Vector3.Transform(new Vector3(0, 0, 0), oTransform), Color.Blue));
            m_oVertexData[2] = (new VertexPositionColor(Vector3.Transform(new Vector3(0, 0, 0), oTransform), oNegative));
            m_oVertexData[3] = (new VertexPositionColor(Vector3.Transform(new Vector3(0, 0, -size), oTransform), oNegative));

            m_oVertexData[4] = (new VertexPositionColor(Vector3.Transform(new Vector3(size, 0, 0), oTransform), Color.Red));
            m_oVertexData[5] = (new VertexPositionColor(Vector3.Transform(new Vector3(0, 0, 0), oTransform), Color.Red));
            m_oVertexData[6] = (new VertexPositionColor(Vector3.Transform(new Vector3(0, 0, 0), oTransform), oNegative));
            m_oVertexData[7] = (new VertexPositionColor(Vector3.Transform(new Vector3(-size, 0, 0), oTransform), oNegative));

            m_oVertexData[8] = (new VertexPositionColor(Vector3.Transform(new Vector3(0, size, 0), oTransform), Color.Green));
            m_oVertexData[9] = (new VertexPositionColor(Vector3.Transform(new Vector3(0, 0, 0), oTransform), Color.Green));
            m_oVertexData[10] = (new VertexPositionColor(Vector3.Transform(new Vector3(0, 0, 0), oTransform), oNegative));
            m_oVertexData[11] = (new VertexPositionColor(Vector3.Transform(new Vector3(0, -size, 0), oTransform), oNegative));

            GraphicsDevice oDevice = oGame.GraphicsDevice;
            VertexBuffer vb
                = new VertexBuffer(oDevice, VertexPositionColor.VertexDeclaration, count, BufferUsage.WriteOnly);
            vb.SetData(0, m_oVertexData, 0, count, 0);
            oDevice.SetVertexBuffer(vb);

            m_oEffect.CurrentTechnique.Passes[0].Apply();
            oDevice.DrawPrimitives(PrimitiveType.LineList, 0, count / 2);
        }

        public void UpdateViewMatrix()
        {
            Quaternion qRotation = Quaternion.CreateFromAxisAngle(VectorHelper.GLOBAL_UP, CurrentRotation_Azimuth)
                * Quaternion.CreateFromAxisAngle(VectorHelper.GLOBAL_RIGHT, CurrentRotation_Elevation);

            Matrix mWorld = Matrix.CreateWorld(Vector3.Zero, -VectorHelper.GLOBAL_BACKWARD, VectorHelper.GLOBAL_UP);
            Matrix mLookAt = Matrix.CreateLookAt(Position, CurrentOrigin, VectorHelper.GLOBAL_UP);

            View = mWorld * mLookAt;
            View = mLookAt;

            Vector3 vPosition = Vector3.Backward * CurrentZoom;
            vPosition = Vector3.Transform(vPosition, qRotation);
            Position = CurrentOrigin + vPosition;
        }

        public void UpdateProjectionMatrix()
        {
            Projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver2, GameInfo.Resolution.Width / GameInfo.Resolution.Height, fVIEW_NEAR, fVIEW_FAR);
        }

        #endregion

        #region Properties

        public Matrix View
        {
            get;
            protected set;
        }
        public Matrix Projection
        {
            get;
            protected set;
        }

        // Location of the Origin
        public Vector3 Origin
        {
            get { return m_vOrigin; }
            set { m_vOrigin = value; }
        }
        private Vector3 CurrentOrigin
        {
            get;
            set;
        }

        // Location of the actual camera
        public Vector3 Position
        {
            get;
            set;
        }

        // Distance from the origin to the camera
        public float Zoom
        {
            get { return m_fZoom; }
            set { m_fZoom = value; }
        }
        public float CurrentZoom
        {
            get;
            set;
        }

        // Rotation around the origin
        public float Rotation_Azimuth
        {
            get { return m_fRotation_Azimuth; }
            set { m_fRotation_Azimuth = value; }
        }
        public float CurrentRotation_Azimuth
        {
            get;
            set;
        }
        public float Rotation_Elevation
        {
            get { return m_fRotation_Elevation; }
            set { m_fRotation_Elevation = value; }
        }
        public float CurrentRotation_Elevation
        {
            get;
            set;
        }
        public Matrix RotationMatrix
        {
            get
            {
                Quaternion qRotation = Quaternion.CreateFromAxisAngle(VectorHelper.GLOBAL_UP, CurrentRotation_Azimuth)
                    * Quaternion.CreateFromAxisAngle(VectorHelper.GLOBAL_RIGHT, CurrentRotation_Elevation);
                return Matrix.CreateFromQuaternion(qRotation);
            }
        }

        public Vector3 CameraForward
        {
            get
            {
                Vector3 oDirection = Origin - Position;
                if (oDirection != Vector3.Zero)
                    oDirection.Normalize();

                return oDirection;
            }
        }
        public Vector3 CameraUp
        {
            get { return Vector3.Transform(VectorHelper.GLOBAL_UP, RotationMatrix); }
        }
        public Vector3 CameraRight
        {
            get { return Vector3.Transform(VectorHelper.GLOBAL_RIGHT, RotationMatrix); }
        }


        public BoundingSphere OrbSphere
        {
            get { return new BoundingSphere(CurrentOrigin, 1.0f); }
        }
        public BoundingSphere RingSphere
        {
            get { return new BoundingSphere(new Vector3(CurrentOrigin.X, 0.0f, CurrentOrigin.Z), fORIGIN_RADIUS); }
        }

        #endregion

        #region Fields

        protected BasicEffect m_oEffect;

        private Vector3 m_vOrigin;
        float m_fRotation_Azimuth;
        float m_fRotation_Elevation;
        float m_fZoom;

        protected const float fVIEW_FAR = 1000.0f;
        protected const float fVIEW_NEAR = 0.010f;
        const float fORIGIN_RADIUS = 4.0f;

        protected Color m_oColor_Ring = Color.White;
        protected Color m_oColor_Orb = Color.White;

        #endregion

    }
}
